package com.heima.wemedia.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.heima.apis.article.IArticleClient;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.tess4j.Tess4jClient;
import com.heima.miniofile.service.MinIoFileStorageService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmSensitive;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsAutoScanService;
import com.heima.wemedia.service.WmNewsSensitiveService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

@Service
@Slf4j
@Transactional
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {
    @Autowired
    private WmNewsMapper wmNewsMapper;
    @Autowired
    private IArticleClient articleClient;
    @Autowired
    private WmChannelMapper wmChannelMapper;
    @Autowired
    private WmUserMapper wmUserMapper;
    @Autowired
    private MinIoFileStorageService fileStorageService;
    @Autowired
    private GreenImageScan greenImageScan;
    @Autowired
    private GreenTextScan greenTextScan;
    @Autowired
    private WmNewsSensitiveService wmNewsSensitiveService;
    @Autowired
    private Tess4jClient tess4j;

    // 存在敏感词
    public static String CONTENT_VIOLATION_MESSAGE = "当前文章中出现违规内容";
    private final List<byte[]> imageList = new CopyOnWriteArrayList<>();

    public static String VIOLATION_MESSAGE; // 文本违规信息

    /**
     * 自媒体文章审核
     *
     * @param id 自媒体文章id
     */
    @Override
    @Async // 标记当前方法是异步方法
    public void autoScanWmNewsAsync(Integer id) throws IOException {
        // 1.查询自媒体文章
        WmNews wmNews = wmNewsMapper.selectById(id);
        if (wmNews == null) {
            throw new RuntimeException("WmNewsAutoScanServiceImpl-文章不存在");
        }

        if (wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode())) {
            // 从内容中提取纯文本内容和图片
            Map<String, Object> textAndImages = handleTextAndImages(wmNews);

            // 审核自定义敏感词过滤
            String pendingContent = String.valueOf(textAndImages.get("content"));
            if (containsSensitiveWords(pendingContent, wmNews)) {
                log.warn("文本内容违规: {}", VIOLATION_MESSAGE);
                return;
            }

            // 2.审核文本内容  阿里云接口
//            if (!handleTextScan(String.valueOf(textAndImages.get("content")), wmNews)) return;

            // 3.审核图片  阿里云接口
            Object imagesObj = textAndImages.get("images");

            // 3.1 将字符url(可能包含多个url)转换为url列表
            List<String> images = convertToUrlList(imagesObj);

            if (!handleImageScan(images, wmNews)) return;

            // 4.审核成功，保存app端的相关的文章数据
            ResponseResult<Long> responseResult = saveAppArticle(wmNews);

            // 记录日志
            String failMsg = String.format("失败, code: %s, msg: %s",
                    Optional.ofNullable(responseResult.getCode()).map(Object::toString).orElse("未知状态"),
                    Optional.ofNullable(responseResult.getErrorMessage()).orElse("无详细信息"));

            log.warn("文章: {}, \n作者: {},\n审核{}",
                    wmNews.getArticleId(),
                    wmNews.getUserId(),
                    Objects.equals(responseResult.getCode(), 200) ? "成功" : failMsg);

            if (!responseResult.getCode().equals(200)) {
                throw new RuntimeException("文章审核，保存app端相关文章数据失败");
            }
            // 回填article_id
            wmNews.setArticleId(responseResult.getData());
            updateWmNews(wmNews, (short) 9, "审核成功");

        }
    }

    /**
     * 将url数据转换成List
     * @param urlData url字符串
     * @return url列表
     */
    private static List<String> convertToUrlList(Object urlData) {
        if (urlData == null) {
            // 返回空数组
            return Collections.emptyList();
        }

        String urlsStr = urlData.toString()
                .replaceAll("^\\[|]$", "")  // 移除首尾方括号
                .trim();                    // 去除首尾空格

        return Arrays.stream(urlsStr.split(",\\s*"))  // 按逗号分割
                .map(String::trim)                   // 去除每个URL的空格
                .filter(url -> !url.isEmpty())       // 过滤空字符串
                .collect(Collectors.toList());       // 转为List
    }

    /**
     * 自媒体文章审核 -- 自定义敏感词过滤
     *
     * @param content 文章内容
     * @param wmNews  文章对象
     * @return true: 包含敏感词  false: 正常内容
     */
    private boolean containsSensitiveWords(String content, WmNews wmNews) {
        // 1. 获取敏感词
        List<String> sensiWords = wmNewsSensitiveService.lambdaQuery()
                .select(WmSensitive::getSensitives)
                .list()
                .stream()
                .map(WmSensitive::getSensitives)
                .collect(Collectors.toList());

        // 2. 初始化敏感词库
        SensitiveWordUtil.initDictionary(sensiWords);

        // 3. 判断是否包含敏感词
        Map<String, Integer> map = SensitiveWordUtil.detectSensitiveWords(content);
        if (!map.isEmpty()) {

            // 将包含敏感词的map转换成字符串
            String details = getViolationMsg(map);

            VIOLATION_MESSAGE = details;
            updateWmNews(wmNews, (short) 2, CONTENT_VIOLATION_MESSAGE + details);
            return true;
        }
        return false;
    }

    /**
     * 将包含敏感词的map转换为字符串指定格式【敏感词：出现次数】
     * 如：敏感词：1,敏感词2：3 ...
     * @param map 包含敏感词的集合
     */
    private static String getViolationMsg(Map<String, Integer> map) {
        return map.entrySet().stream()
                .map(entry -> entry.getKey() + ":" + entry.getValue())
                .collect(Collectors.joining(", ", "【", "】"));
    }

    /**
     * 保存app端相关的文章数据
     *
     * @param wmNews 文章对象
     */
    @Override
    public ResponseResult<Long> saveAppArticle(WmNews wmNews) {

        ArticleDto dto = new ArticleDto();
        // 属性的拷贝
        BeanUtils.copyProperties(wmNews, dto);
        // 文章的布局
        dto.setLayout(Integer.valueOf(wmNews.getType()));
        // 频道
        WmChannel wmChannel = wmChannelMapper.selectById(wmNews.getChannelId());

        if (wmChannel != null) dto.setChannelName(wmChannel.getName());

        // 作者
        dto.setAuthorId((int) wmNews.getUserId().longValue());
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());

        if (wmUser != null) dto.setAuthorName(wmUser.getName());


        // 设置文章id
        if (wmNews.getArticleId() != null) dto.setId(wmNews.getArticleId());

        dto.setCreatedTime(LocalDateTime.now());

        // 调用 文章微服务 保存文章
        return articleClient.saveArticle(dto);

    }


    /**
     * 审核图片
     *
     * @param images 图片列表
     * @param wmNews 文章对象
     * @return true:审核成功/无需审核  false:审核失败
     */
    private boolean handleImageScan(List<String> images, WmNews wmNews) {

        if (images == null || images.isEmpty()) return true;

        // 下载图片 minIo
        // 图片去重 -- 封面图片可能是文章内容中的图片
        images = images.stream()
                .map(String::trim)
                .filter(url -> !url.isEmpty())
                .distinct()
                .collect(Collectors.toList());

        for (String imageUrl : images) {
            if (!processImage(imageUrl, wmNews)) {
                return false;
            }
        }

        // 调用aliyun接口再次审核图片
//        try {
//            Map<String, String> map = greenImageScan.imageScan(imageList);
//            return auditTextAndImages(wmNews, map);
//        } catch (Exception e) {
//            log.error("图片审核异常: {}", e.getMessage());
//            return false;
//        }
        return true;
    }

    /**
     * 处理单张图片：下载 + OCR识别 + 敏感内容审核
     * @param imageUrl 图片URL
     * @param wmNews 文章对象（用于关联审核结果）
     * @return 审核是否通过（true=通过，false=违规）
     */
    private boolean processImage(String imageUrl, WmNews wmNews) {
        try {
            // 1. 下载图片并加载
            byte[] imageBytes = fileStorageService.downLoadFile(imageUrl);
            BufferedImage bufferedImage = ImageIO.read(new URL(imageUrl));

            // 2. 校验图片有效性
            if (bufferedImage == null) {
                log.error("图片加载失败: {}", imageUrl);
                return false;
            }

            // 3. OCR识别
            String ocrText = tess4j.doOCR(bufferedImage);

            // 4. 敏感内容审核
            if (containsSensitiveWords(ocrText, wmNews)) {
                log.warn("图片违规: {}", imageUrl);
                return false;
            }

            // 5. 保留图片数据（用于后续处理）
            imageList.add(imageBytes);
            return true;

        } catch (Exception e) {
            log.error("图片处理异常 - URL: {}, 错误: {}", imageUrl, e.getMessage(), e);
            return false; // 或根据业务需求抛出异常
        }
    }

    /**
     * 审核内容
     *
     * @param wmNews 文章对象
     * @param map    审核结果
     */
    private boolean auditTextAndImages(WmNews wmNews, Map<String, String> map) {

        if (map == null || map.isEmpty()) return true;

        //  未开通服务
        String noService = "You have not opened Yundun Content Moderation Service";
        if (noService.contains(map.get("msg"))) {
            log.warn("WmNewsAutoScanServiceImpl-未开通服务: {}", map.get("msg"));
            return false;
        }

        // 审核失败
        if ("block".equals(map.get("suggestion"))) {
            updateWmNews(wmNews, (short) 2, "当前文章中存在违规内容");
            log.info("WmNewsAutoScanServiceImpl-审核失败: {}", map.get("msg"));
            return false;
        }

        // 不确定信息  需要人工审核
        if ("review".equals(map.get("suggestion"))) {
            updateWmNews(wmNews, (short) 3, "当前文章中存在不确定内容");
            log.info("WmNewsAutoScanServiceImpl-不确定信息: {}", map.get("msg"));
            return false;
        }
        return true;
    }

    /**
     * 审核纯文本内容
     *
     * @param content 文本内容
     * @param wmNews  文章对象
     */
    private boolean handleTextScan(String content, WmNews wmNews) {

        if ((wmNews.getTitle() + content).isEmpty()) return true;

        try {
            Map<String, String> map = greenTextScan.greeTextScan((wmNews.getTitle() + "-" + content));
            return auditTextAndImages(wmNews, map);
        } catch (Exception e) {
            log.error("WmNewsAutoScanServiceImpl-文本审核异常: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 修改文章内容
     *
     * @param wmNews 文章对象
     * @param status 状态
     * @param reason 原因
     */
    private void updateWmNews(WmNews wmNews, short status, String reason) {
        wmNews.setStatus(status);
        wmNews.setReason(reason);
        wmNewsMapper.updateById(wmNews);
    }

    /**
     * 1.从自媒体文章的内容中提取文本和图片
     * 2.提取文章的封面图片
     *
     * @param wmNews 文章对象
     */
    private Map<String, Object> handleTextAndImages(WmNews wmNews) throws JsonProcessingException {

        // 存储纯文本内容
        ObjectMapper objectMapper = new ObjectMapper();

        List<Map<String, Object>> maps = objectMapper.readValue(
                wmNews.getContent(),
                new TypeReference<List<Map<String, Object>>>() {
                }
        );

        // 1.从自媒体文章的内容中提取文本和图片
        String content = maps.stream()
                .filter(map -> "text".equals(map.get("type")))
                .map(map -> (String) map.get("value"))
                .collect(Collectors.joining());

        List<String> images = maps.stream()
                .filter(map -> "image".equals(map.get("type")))
                .map(map -> (String) map.get("value"))
                .collect(Collectors.toList());


        // 2.提取文章的封面图片
        if (StringUtils.isNotBlank(wmNews.getImages())) {
            String[] split = wmNews.getImages().split(",");
            images.addAll(Arrays.asList(split));
        }

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("content", content);
        resultMap.put("images", images);
        return resultMap;

    }
}
